/*
Acess v0.1
Binary Loader Code
*/
#include <system.h>
#include <acess.h>
#include <binload.h>

#define	BIN_DEBUG	0
#define	DEBUG	((BIN_DEBUG)|(ACESS_DEBUG))

#include <dbg_defs.h>

// === CONSTANTS ===
#define BIN_LOWEST	0x100000		// 1MiB
#define BIN_GRANUALITY	0x10000		// 64KiB
#define BIN_HIGHEST	(0xC0000000-BIN_GRANUALITY)		// 3GiB - 1 Granuality (Just below the kernel)
#define	KLIB_LOWEST	0xC8000000
#define KLIB_GRAN	0x8000	// 32KiB
#define	KLIB_HIGHEST	(0xE0000000-KLIB_GRAN)

// === IMPORTS ===
// --- ELF ---
extern tLoadedBin	*Elf_Load(int fp);
extern int	Elf_Relocate(Uint Base);
extern int	Elf_GetSymbol(Uint Base, char *Name, Uint *ret);
extern tLoadedBin	*PE_Load(int fp);
// --- Memory Manager ---
extern void 	MM_SetCOW(Uint addr);
extern void 	MM_SetKernel(Uint addr);
extern void 	MM_RefPage(Uint addr);

// === GLOBALS ===
tLoadedBin	*glLoadedBinaries = NULL;
char	**gsaRegInterps = NULL;
 int	giRegInterps = 0;
tKernelBin	*glLoadedKernelLibs;
tBinType	gBintypePE = {
		0x5A4D /*MZ*/, 0xFFFF, "PE",
		PE_Load, NULL, NULL,	// PE is currently user-only
		NULL
	};
tBinType	gBintypeElf = {
		0x464C457F /*\x7F ELF*/, -1, "ELF",
		Elf_Load, Elf_Relocate, Elf_GetSymbol,
		&gBintypePE
	};
tBinType	*gRegBinTypes = &gBintypeElf;

// === PROTOTYPES ===
Uint	Binary_Load(char *file, Uint *entryPoint);
tLoadedBin *Binary_GetInfo(char *truePath);
Uint	Binary_MapIn(tLoadedBin *binary);
Uint	Binary_IsMapped(tLoadedBin *binary);
tLoadedBin *Binary_DoLoad(char *truePath);
void	Binary_Dereference(tLoadedBin *Info);
 int	Binary_Relocate(Uint Base);
 
// === FUNCTIONS ===
Uint Binary_Load(char *file, Uint *entryPoint)
{
	char	*sTruePath;
	tLoadedBin	*pBinary;
	Uint	base = -1;

	DEBUGS("Binary_Load: (file='%s')\n", file);
	
	// Sanity Check Argument
	if(file == NULL)	return 0;

	// Get True File Path
	sTruePath = VFS_GetTruePath(file);
	
	if(sTruePath == NULL) {
		WarningEx("BIN", "'%s' does not exist.", file);
		return 0;
	}
	
	DEBUGS(" Binary_Load: sTruePath = '%s'\n", sTruePath);

	// Check if the binary has already been loaded
	if( !(pBinary = Binary_GetInfo(sTruePath)) )
		pBinary = Binary_DoLoad(sTruePath);	// Else load it
	
	// Clean Up
	free(sTruePath);
	
	// Error Check
	if(pBinary == NULL) {
		return 0;
	}
	
	#if 0
	if( (base = Binary_IsMapped(pBinary)) )
		return base;
	#endif
	
	// Map into process space
	base = Binary_MapIn(pBinary);	// If so then map it in
	
	// Check for errors
	if(base == 0)	return 0;
	
	// Interpret
	if(pBinary->Interpreter) {
		Uint start;
		if( Binary_Load(pBinary->Interpreter, &start) == 0 )
			return 0;
		*entryPoint = start;
	}
	else
		*entryPoint = pBinary->EntryPoint - pBinary->Base + base;
	
	// Return
	DEBUGS("Binary_Load: RETURN 0x%x, *entryPoint = 0x%x\n", base, *entryPoint);
	return base;	// Pass the base as an argument to the user if there is an interpreter
}

/**
 \fn tLoadedBin *Binary_GetInfo(char *truePath)
 \brief Finds a matching binary entry
 \param truePath	File Identifier (True path name)
*/
tLoadedBin *Binary_GetInfo(char *truePath)
{
	tLoadedBin	*pBinary;
	pBinary = glLoadedBinaries;
	while(pBinary)
	{
		if(strcmp(pBinary->TruePath, truePath) == 0)
			return pBinary;
		pBinary = pBinary->Next;
	}
	return NULL;
}

/**
 \fn Uint Binary_MapIn(tLoadedBin *binary)
 \brief Maps an already-loaded binary into an address space.
 \param binary	Pointer to globally stored data.
*/
Uint Binary_MapIn(tLoadedBin *binary)
{
	Uint	base;
	Uint	addr;
	 int	i;
	
	// Reference Executable (Makes sure that it isn't unloaded)
	binary->ReferenceCount ++;
	
	// Get Binary Base
	base = binary->Base;
	
	// Check if base is free
	if(base != 0)
	{
		for(i=0;i<binary->PageCount;i++)
		{
			if( MM_IsValid( binary->Pages[i].Virtual & ~0xFFF ) ) {
				base = 0;
				LogF(" Binary_MapIn: Address 0x%x is taken\n", binary->Pages[i].Virtual & ~0xFFF);
				break;
			}
		}
	}
	
	// Check if the executable has no base or it is not free
	if(base == 0)
	{
		// If so, give it a base
		base = BIN_HIGHEST;
		while(base >= BIN_LOWEST)
		{
			for(i=0;i<binary->PageCount;i++)
			{
				addr = binary->Pages[i].Virtual & ~0xFFF;
				addr -= binary->Base;
				addr += base;
				if( MM_IsValid( addr ) )	break;
			}
			// If space was found, break
			if(i == binary->PageCount)		break;
			// Else decrement pointer and try again
			base -= BIN_GRANUALITY;
		}
	}
	
	// Error Check
	if(base < BIN_LOWEST) {
		WarningEx("BIN", "Executable '%s' cannot be loaded into PID %i, no space",
			binary->TruePath, proc_pid);
		return 0;
	}
	
	// Map Executable In
	for(i=0;i<binary->PageCount;i++)
	{
		addr = binary->Pages[i].Virtual & ~0xFFF;
		addr -= binary->Base;
		addr += base;
		DEBUGS(" Binary_MapIn: %i - 0x%x to 0x%x\n", i, addr, binary->Pages[i].Physical);
		mm_map( addr, (Uint) (binary->Pages[i].Physical) );
		if( binary->Pages[i].Physical & 1)	// Read-Only
			mm_setro( addr, 1 );
		else
			MM_SetCOW( addr );
	}
	
	//LogF(" Binary_MapIn: *0x%x = 0x%x\n", binary->Pages[0].Virtual, *(Uint*)binary->Pages[0].Virtual);
	
	return base;
}

#if 0
/**
 * \fn Uint Binary_IsMapped(tLoadedBin *binary)
 * \brief Check if a binary is already mapped into the address space
 * \param binary	Binary information to check
 * \return Current Base or 0
 */
Uint Binary_IsMapped(tLoadedBin *binary)
{
	Uint	iBase;
	
	// Check prefered base
	iBase = binary->Base;
	if(MM_GetPage( iBase ) == (binary->Pages[0].Physical & ~0xFFF))
		return iBase;
	
	for(iBase = BIN_HIGHEST;
		iBase >= BIN_LOWEST;
		iBase -= BIN_GRANUALITY)
	{
		if(MM_GetPage( iBase ) == (binary->Pages[0].Physical & ~0xFFF))
			return iBase;
	}
	
	return 0;
}
#endif

/**
 \fn tLoadedBin *Binary_DoLoad(char *truePath)
 \brief Loads a binary file into memory
 \param truePath	Absolute filename of binary
*/
tLoadedBin *Binary_DoLoad(char *truePath)
{
	tLoadedBin	*pBinary;
	 int	fp, i;
	Uint	ident, addr;
	tBinType	*bt = gRegBinTypes;
	
	DEBUGS("Binary_DoLoad: (truePath='%s')\n", truePath);
	
	// Open File
	fp = vfs_open(truePath, VFS_OPENFLAG_READ);
	if(fp == -1) {
		DEBUGS(" Binary_DoLoad: Unable to load file, access denied\n");
		return NULL;
	}
	
	// Read File Type
	vfs_read(fp, 4, &ident);
	vfs_seek(fp, 0, 1);
	
	for(; bt; bt = bt->Next)
	{
		if( (ident & bt->Mask) != (Uint)bt->Ident )
			continue;
		pBinary = bt->Load(fp);
		break;
	}
	if(!bt) {
		WarningEx("BIN", "'%s' is an unknown file type. (0x%x 0x%x 0x%x 0x%x)",
			truePath, ident&0xFF, ident>>8, ident>>16, ident>>24);
		return 0;
	}
	
	// Error Check
	if(pBinary == NULL)	return NULL;
	
	// Initialise Structure
	pBinary->ReferenceCount = 0;
	pBinary->TruePath = malloc( strlen(truePath) + 1 );
	strcpy(pBinary->TruePath, truePath);
	
	// Debug Information
	DEBUGS(" Binary_DoLoad: Interpreter: '%s'\n", pBinary->Interpreter);
	DEBUGS(" Binary_DoLoad: Base: 0x%x, Entry: 0x%x\n", pBinary->Base, pBinary->EntryPoint);
	DEBUGS(" Binary_DoLoad: PageCount: %i\n", pBinary->PageCount);
	
	// Read Data
	for(i=0;i<pBinary->PageCount;i++)
	{
		addr = (Uint)MM_AllocPhys();
		MM_RefPage( addr );	// Make sure it is _NOT_ freed until we want it to be
		ident = MM_MapTemp( addr );
		ident += pBinary->Pages[i].Virtual & 0xFFF;
		
		/*if( (pBinary->Pages[i].Virtual & 0xFFF) + (pBinary->Pages[i].Size & 0xFFF) > 4096) {
			WarningEx("BIN ", "DoLoad - Page %i has an offset+size of greater than a page\n", i);
			continue;
		}*/
		
		if(pBinary->Pages[i].Physical == -1) {
			DEBUGS(" Binary_DoLoad: %i - ZERO\n", i);
			memsetda( (void*)ident, 0, 1024 );
		} else {
			vfs_seek( fp, pBinary->Pages[i].Physical, 1 );
			if(pBinary->Pages[i].Size != 0x1000) {
				DEBUGS(" Binary_DoLoad: %i - 0x%x - 0x%x bytes\n",
					i, pBinary->Pages[i].Physical, pBinary->Pages[i].Size);
				memsetda( (void*)ident, 0, 1024 );
				vfs_read( fp, pBinary->Pages[i].Size, (void*)ident );
			} else {
				DEBUGS(" Binary_DoLoad: %i - 0x%x\n", i, pBinary->Pages[i].Physical);
				vfs_read( fp, 0x1000, (void*)ident );
			}
		}
		pBinary->Pages[i].Physical = addr;
		MM_FreeTemp( ident );
	}
	DEBUGS(" Binary_DoLoad: Page Count: %i\n", pBinary->PageCount);
	
	// Close File
	vfs_close(fp);
	
	// Add to the list
	LOCK();
	pBinary->Next = glLoadedBinaries;
	glLoadedBinaries = pBinary;
	UNLOCK();
	
	// Return
	return pBinary;
}

/**
 * \fn void Binary_Unload(Uint Base)
 * \brief Unload / Unmap a binary
 * \param Base	Loaded Base
 * \note Currently used only for kernel libaries
 */
void Binary_Unload(Uint Base)
{
	tKernelBin	*pKBin;
	tKernelBin	*prev = NULL;
	 int	i;
	
	if(Base < 0xC0000000)
	{
		// TODO: User Binaries
		WarningEx("BIN", "Unloading user binaries is currently unimplemented");
		return;
	}
	
	// Kernel Libraries
	for(pKBin = glLoadedKernelLibs;
		pKBin;
		prev = pKBin, pKBin = pKBin->Next)
	{
		// Check the base
		if(pKBin->Base != Base)	continue;
		// Deallocate Memory
		for(i = 0; i < pKBin->Info->PageCount; i++) {
			mm_dealloc( Base + (i << 12) );
		}
		// Dereference Binary
		Binary_Dereference( pKBin->Info );
		// Remove from list
		if(prev)	prev->Next = pKBin->Next;
		else		glLoadedKernelLibs = pKBin->Next;
		// Free Kernel Lib
		free(pKBin);
		return;
	}
}

/**
 * \fn void Binary_Dereference(tLoadedBin *Info)
 * \brief Dereferences and if nessasary, deletes a binary
 * \param Info	Binary information structure
 */
void Binary_Dereference(tLoadedBin *Info)
{
	// Decrement reference count
	Info->ReferenceCount --;
	
	// Check if it is still in use
	if(Info->ReferenceCount)	return;
	
	/// \todo Implement binary freeing
}

/**
 \fn char *Binary_RegInterp(char *path)
 \brief Registers an Interpreter
 \param path	Path to interpreter provided by executable
*/
char *Binary_RegInterp(char *path)
{
	 int	i;
	// NULL Check Argument
	if(path == NULL)	return NULL;
	// NULL Check the array
	if(gsaRegInterps == NULL)
	{
		giRegInterps = 1;
		gsaRegInterps = malloc( sizeof(char*) );
		gsaRegInterps[0] = malloc( strlen(path) );
		strcpy(gsaRegInterps[0], path);
		return gsaRegInterps[0];
	}
	
	// Scan Array
	for( i = 0; i < giRegInterps; i++ )
	{
		if(strcmp(gsaRegInterps[i], path) == 0)
			return gsaRegInterps[i];
	}
	
	// Interpreter is not in list
	giRegInterps ++;
	gsaRegInterps = malloc( sizeof(char*)*giRegInterps );
	gsaRegInterps[i] = malloc( strlen(path) );
	strcpy(gsaRegInterps[i], path);
	return gsaRegInterps[i];
}

// ============
// Kernel Binary Handling
// ============

/**
 * \fn Uint Binary_LoadKernel(char *path)
 * \brief Load a binary into kernel space
 * \note This function shares much with #Binary_Load, but does it's own mapping
 */
Uint Binary_LoadKernel(char *file)
{
	char	*sTruePath;
	tLoadedBin	*pBinary;
	tKernelBin	*pKBinary;
	Uint	base = -1;
	Uint	addr;
	 int	i;

	DEBUGS("Binary_LoadKernel: (file='%s')\n", file);
	
	// Sanity Check Argument
	if(file == NULL)	return 0;

	// Get True File Path
	sTruePath = VFS_GetTruePath(file);
	if(sTruePath == NULL) {
		return 0;
	}
	
	// Check if the binary has already been loaded
	if( (pBinary = Binary_GetInfo(sTruePath)) )
	{
		for(pKBinary = glLoadedKernelLibs;
			pKBinary;
			pKBinary = pKBinary->Next )
		{
			if(pKBinary->Info == pBinary)
				return pKBinary->Base;
		}
	}
	else
		pBinary = Binary_DoLoad(sTruePath);	// Else load it
	
	// --------------
	// Now pBinary is valid (either not mapped in K-Space, or freshly loaded)
	// So, map it into kernel space
	// --------------
	
	// Reference Executable (Makes sure that it isn't unloaded)
	pBinary->ReferenceCount ++;
	
	// Check compiled base
	base = pBinary->Base;
	// - Sanity Check
	if(base < KLIB_LOWEST || base > KLIB_HIGHEST || base + (pBinary->PageCount<<12) > KLIB_HIGHEST) {
		base = 0;
	}
	// - Check if it is a valid base address
	if(base != 0)
	{
		for(i=0;i<pBinary->PageCount;i++)
		{
			if( MM_IsValid( pBinary->Pages[i].Virtual & ~0xFFF ) ) {
				base = 0;
				LogF(" Binary_MapIn: Address 0x%x is taken\n", pBinary->Pages[i].Virtual & ~0xFFF);
				break;
			}
		}
	}
	
	// Check if the executable has no base or it is not free
	if(base == 0)
	{
		// If so, give it a base
		base = KLIB_LOWEST;
		while(base < KLIB_HIGHEST)
		{
			for(i = 0; i < pBinary->PageCount; i++)
			{
				addr = pBinary->Pages[i].Virtual & ~0xFFF;
				addr -= pBinary->Base;
				addr += base;
				if( MM_IsValid( addr ) )	break;
			}
			// If space was found, break
			if(i == pBinary->PageCount)		break;
			// Else decrement pointer and try again
			base += KLIB_GRAN;
		}
	}
	
	// - Error Check
	if(base >= KLIB_HIGHEST) {
		WarningEx("BIN", "Executable '%s' cannot be loaded into PID %i, no space",
			pBinary->TruePath, proc_pid);
		Binary_Dereference( pBinary );
		return 0;
	}
	
	// - Map binary in
	for(i = 0; i < pBinary->PageCount; i++)
	{
		addr = pBinary->Pages[i].Virtual & ~0xFFF;
		addr -= pBinary->Base;
		addr += base;
		DEBUGS(" Binary_DoLoad: %i - 0x%x to 0x%x\n", i, addr, pBinary->Pages[i].Physical);
		mm_map( addr, (Uint) (pBinary->Pages[i].Physical) );
		MM_SetKernel( addr );
		if( pBinary->Pages[i].Physical & 1)	// Read-Only
			mm_setro( addr, 1 );
		else
			MM_SetCOW( addr );
	}

	// Relocate Library
	if( !Binary_Relocate( base ) )
	{
		WarningEx("BIN", "Relocation of '%s' failed, unloading", sTruePath);
		Binary_Unload( base );
		Binary_Dereference( pBinary );
		return 0;
	}
	
	// Add to list (relocator must look at itself manually, not via Binary_GetSymbol)
	pKBinary = malloc(sizeof(*pKBinary));
	pKBinary->Base = base;
	pKBinary->Info = pBinary;
	LOCK();
	pKBinary->Next = glLoadedKernelLibs;
	glLoadedKernelLibs = pKBinary;
	UNLOCK();
	
	return base;
}

/**
 * \fn void Binary_Relocate(Uint Base)
 * \brief Relocates a loaded binary (used by kernel libraries)
 * \param Base	Loaded base address of binary
 * \return Boolean Success
 */
int Binary_Relocate(Uint Base)
{
	Uint32	ident = *(Uint32*) Base;
	tBinType	*bt = gRegBinTypes;
	
	for(; bt; bt = bt->Next)
	{
		if( (ident & bt->Mask) == (Uint)bt->Ident )
			return bt->Relocate(Base);
	}
	
	WarningEx("BIN", "0x%x is an unknown file type. (0x%x 0x%x 0x%x 0x%x)",
		Base, ident&0xFF, ident>>8, ident>>16, ident>>24);
	return 0;
}

/**
 * \fn Uint Binary_GetSymbol(char *Name)
 * \brief Get a symbol value
 * \return Value of symbol or -1 on error
 * 
 * Gets the value of a symbol from either the currently loaded
 * libraries or the kernel's exports.
 */
Uint Binary_GetSymbol(char *Name)
{
	Uint	ret;
	if( Binary_GetSymbolEx(Name, &ret) )
		return ret;
	return -1;
}

/**
 * \fn Uint Binary_GetSymbolEx(char *Name, Uint *Value)
 * \brief Get a symbol value
 * 
 * Gets the value of a symbol from either the currently loaded
 * libraries or the kernel's exports.
 */
Uint Binary_GetSymbolEx(char *Name, Uint *Value)
{
	 int	i;
	tKernelBin	*pKBin;
	
	// Scan Kernel
	for( i = 0; i < NUM_KERNEL_EXPORTS; i++ )
	{
		if(strcmp(Name, gKernelExports[i].Name) == 0) {
			*Value = gKernelExports[i].Value;
			return 1;
		}
	}
	
	// Scan Loaded Libraries
	for(pKBin = glLoadedKernelLibs;
		pKBin;
		pKBin = pKBin->Next )
	{
		if( Binary_GetSymbolBin(pKBin->Base, Name, Value) ) {
			return 1;
		}
	}
	
	WarningEx("BIN", "Unable to find symbol '%s'", Name);
	return 0;
}

/**
 * \fn Uint Binary_GetSymbolBin(Uint Base, char *Name, Uint *val)
 * \brief Get a symbol from the specified library
 * \param Base	Base address
 * \param Name	Name of symbol to find
 * \param val	Pointer to place final value
 */
Uint Binary_GetSymbolBin(Uint Base, char *Name, Uint *val)
{
	Uint32	ident = *(Uint32*) Base;
	tBinType	*bt = gRegBinTypes;
	
	for(; bt; bt = bt->Next)
	{
		if( (ident & bt->Mask) == (Uint)bt->Ident )
			return bt->GetSymbol(Base, Name, val);
	}
	
	WarningEx("BIN", "0x%x is an unknown file type. (0x%x 0x%x 0x%x 0x%x)",
		Base, ident&0xFF, ident>>8, ident>>16, ident>>24);
	return 0;
}
